import copy
from pymysql import Connection  # 导入数据库连接包
from datetime import datetime, timedelta
import numpy as np
from docplex.mp.model import Model  # 导出库，只用这一个就够了

def single_transportation_plan(single_transportation_plan_condition):
    # 这个类只创建一个对象，并保存所有的数据到这个对象上
    class Star:
        pass

    class NuclearPowerPlant:
        def __init__(self, id, nuclear_power_plant_name, nuclear_power_plant_describe, create_time, update_time):
            self.id = id
            self.nuclear_power_plant_name = nuclear_power_plant_name
            self.nuclear_power_plant_describe = nuclear_power_plant_describe
            self.create_time = create_time
            self.update_time = update_time

            self.unit_list = []

    class Unit:
        def __init__(self, id, unit_name, unit_describe, nuclear_power_plant_id, create_time, update_time):
            self.id = id
            self.unit_name = unit_name
            self.unit_describe = unit_describe
            self.nuclear_power_plant_id = nuclear_power_plant_id
            self.create_time = create_time
            self.update_time = update_time

            self.non_loading_time_window_list = []
            self.upper_limit_of_empty_containers = 0  # 机组空容器数量的上限

    class NonLoadingTimeWindow:
        def __init__(self, id, non_loading_start_time, non_loading_end_time, non_loading_time_window_describe, unit_id,
                     create_time, update_time):
            self.id = id
            self.non_loading_start_time = non_loading_start_time
            self.non_loading_end_time = non_loading_end_time
            self.non_loading_time_window_describe = non_loading_time_window_describe
            self.unit_id = unit_id
            self.create_time = create_time
            self.update_time = update_time

    class ShippingTime:
        def __init__(self, id, start_point_id, end_point_id, shipping_time_value, create_time, update_time):
            self.id = id
            self.start_point_id = start_point_id
            self.end_point_id = end_point_id
            self.shipping_time_value = shipping_time_value
            self.create_time = create_time
            self.update_time = update_time

    # select id, horizontal_axis, width, vertical_axis, color, start_date, end_date, unit_name, version_id, create_time, update_time from star_rectangle
    class Rectangle:
        def __init__(self, id, horizontal_axis, width, vertical_axis, color, start_date, end_date, unit_name, version_id, create_time, update_time):
            self.id = id
            self.horizontal_axis = horizontal_axis
            self.width = width
            self.vertical_axis = vertical_axis
            self.color = color
            self.start_date = start_date
            self.end_date = end_date
            self.unit_name = unit_name
            self.version_id = version_id
            self.create_time = create_time
            self.update_time = update_time

        def to_dict(self):
            return {
                "id": self.id,
                "horizontalAxis": self.horizontal_axis,
                "width": self.width,
                "verticalAxis": self.vertical_axis,
                "color": self.color,
                "startDate": self.start_date.strftime("%Y-%m-%d"),
                "endDate": self.end_date.strftime("%Y-%m-%d"),
                "unitName": self.unit_name,
                "versionId": self.version_id,
                "createTime": "",
                "updateTime": ""
            }

    # select id, version_name, nuclear_power_plant_name, start_date_of_plan, end_date_of_plan, upper_limit_days, create_time, update_time from star_single_transportation_plan_version
    class SingleTransportationPlanVersion:
        def __init__(self, id, version_name, nuclear_power_plant_name, start_date_of_plan, end_date_of_plan,
                     upper_limit_days, create_time, update_time):
            self.id = id
            self.version_name = version_name
            self.nuclear_power_plant_name = nuclear_power_plant_name
            self.start_date_of_plan = start_date_of_plan
            self.end_date_of_plan = end_date_of_plan
            self.upper_limit_days = upper_limit_days
            self.create_time = create_time
            self.update_time = update_time

        def to_dict(self):
            return {
                "id": self.id,
                "versionName": self.version_name,
                "nuclearPowerPlantName": self.nuclear_power_plant_name,
                # 将日期转换为字符串
                "startDateOfPlan": self.start_date_of_plan.strftime("%Y-%m-%d"),
                "endDateOfPlan": self.end_date_of_plan.strftime("%Y-%m-%d"),
                "upperLimitDays": self.upper_limit_days,
                "createTime": "",
                "updateTime": ""
            }

    # try:
    # 运输计划开始的日期(运输计划的起点是HYH的码头，即装有空容器的船离开码头的日期)
    start_date = single_transportation_plan_condition["startDate"]

    # 运输计划的上限天数(指定为180天，算法就会计算180内如果能完成，则给出方案。这个变量只需设置为任意大的数值，因为优化算法是目标是最小化计划的天数)
    upper_limit_days = single_transportation_plan_condition["upperLimitDays"]

    total_number_of_empty_containers = single_transportation_plan_condition["totalNumberOfEmptyContainers"]  # 总的空容器数量

    # 作业面的数量（即并行的作业数量）
    number_of_working_surfaces = single_transportation_plan_condition["numberOfWorkingSurfaces"]

    # 核电站列表
    nuclear_power_plant_list = []
    # 目标核电站，即装乏燃料的核电站
    target_nuclear_power_plant = None
    for e_nuclear_power_plant in single_transportation_plan_condition["nuclearPowerPlantList"]:
        c_nuclear_power_plant = NuclearPowerPlant(e_nuclear_power_plant["id"], e_nuclear_power_plant["nuclearPowerPlantName"], e_nuclear_power_plant["nuclearPowerPlantDescribe"], "","")
        nuclear_power_plant_list.append(c_nuclear_power_plant)
        if e_nuclear_power_plant["id"] == single_transportation_plan_condition["targetNuclearPowerPlantId"]:
            target_nuclear_power_plant = c_nuclear_power_plant

    # 装乏燃料的核电站的所有机组，各机组有可安排的最大空容器数量上限
    unit_list_of_target_nuclear_power_plant = []
    for e_unit in single_transportation_plan_condition["unitListByNuclearPowerPlantId"]:
        c_unit = Unit(e_unit["id"], e_unit["unitName"], e_unit["unitDescribe"], e_unit["nuclearPowerPlantId"], "","")
        c_unit.upper_limit_of_empty_containers = e_unit["upperLimitOfEmptyContainers"]
        # 未给出值的话，设置为999999，不做限制
        if e_unit["upperLimitOfEmptyContainers"] is None or e_unit["upperLimitOfEmptyContainers"] == "":
            c_unit.upper_limit_of_empty_containers = 999999
        unit_list_of_target_nuclear_power_plant.append(c_unit)

    star = Star()  # 只创建一个对象，并保存所有的数据到这个对象上
    star.start_date = datetime.strptime(start_date, '%Y-%m-%d').date()
    star.upper_limit_days = upper_limit_days
    star.total_number_of_empty_containers = total_number_of_empty_containers
    star.number_of_working_surfaces = number_of_working_surfaces
    star.nuclear_power_plant_list = nuclear_power_plant_list
    star.target_nuclear_power_plant = target_nuclear_power_plant
    star.unit_list_of_target_nuclear_power_plant = unit_list_of_target_nuclear_power_plant
    star.end_date = star.start_date + timedelta(days=upper_limit_days - 1)  # 比如上限天数为2天，结束日期为开始日期 + 1





    # 可以全部数据都从前端获取（是否需要看部署）
    # 连接数据库
    conn = Connection(
        host="192.168.56.10",
        port=3306,
        user="root",
        password="root"
    )

    cursor = conn.cursor()  # 创建游标对象
    conn.select_db("feima_star")  # 使用数据库

    cursor.execute("select id, non_loading_start_time, non_loading_end_time, non_loading_time_window_describe, unit_id, create_time, update_time from star_non_loading_time_window")
    star.non_loading_time_window_list = [NonLoadingTimeWindow(e_non_loading_time_window[0], e_non_loading_time_window[1], e_non_loading_time_window[2], e_non_loading_time_window[3], e_non_loading_time_window[4], e_non_loading_time_window[5], e_non_loading_time_window[6]) for e_non_loading_time_window in cursor.fetchall()]

    cursor.execute("select id, start_point_id, end_point_id, shipping_time_value, create_time, update_time from star_shipping_time")
    star.shipping_time_list = [ShippingTime(e_shipping_time[0], e_shipping_time[1], e_shipping_time[2], e_shipping_time[3], e_shipping_time[4], e_shipping_time[5]) for e_shipping_time in cursor.fetchall()]

    conn.close()






    # 得到每个核电站的机组列表
    for e_nuclear_power_plant in star.nuclear_power_plant_list:
        for e_unit in star.unit_list_of_target_nuclear_power_plant:
            if e_unit.nuclear_power_plant_id == e_nuclear_power_plant.id:
                e_nuclear_power_plant.unit_list.append(e_unit)

    # 得到每个机组的不可装料时间窗列表（计划时间段内的时间窗）
    for e_unit in star.unit_list_of_target_nuclear_power_plant:
        for e_non_loading_time_window in star.non_loading_time_window_list:
            if e_non_loading_time_window.unit_id == e_unit.id:
                # 筛选出压住计划时间段临界线的时间窗和在计划时间段内的时间窗，并且压住计划时间段临界线的时间窗只截取在计划时间段内的部分
                if e_non_loading_time_window.non_loading_start_time < star.start_date and e_non_loading_time_window.non_loading_end_time > star.start_date:
                    e_non_loading_time_window.non_loading_start_time = star.start_date
                    e_unit.non_loading_time_window_list.append(e_non_loading_time_window)
                if e_non_loading_time_window.non_loading_start_time < star.end_date and e_non_loading_time_window.non_loading_end_time > star.end_date:
                    e_non_loading_time_window.non_loading_end_time = star.end_date
                    e_unit.non_loading_time_window_list.append(e_non_loading_time_window)
                if e_non_loading_time_window.non_loading_start_time >= star.start_date and e_non_loading_time_window.non_loading_end_time <= star.end_date:
                    e_unit.non_loading_time_window_list.append(e_non_loading_time_window)

    # 运输计划的起点是HYH的码头，这个是固定的。运输计划是从货船装空容器离开码头开始算起的。
    # star.nuclear_power_plant_list[0].id 表示添加的第一个核电站的码头为运输计划的起点
    # 获取从HYH的码头到装乏燃料的核电站的码头的海运时间
    shipping_time_from_star_point_to_target_nuclear_power_plant = 0
    for e_shipping_time in star.shipping_time_list:
        if e_shipping_time.start_point_id == star.nuclear_power_plant_list[0].id and e_shipping_time.end_point_id == star.target_nuclear_power_plant.id:
            shipping_time_from_star_point_to_target_nuclear_power_plant = e_shipping_time.shipping_time_value

    # 获取将空容器从货船搬运到码头的公海换装时间(一个作业面一天能搬运2个空容器)
    time_of_transport_empty_containers_from_cargo_ship_to_the_dock = np.ceil(
        star.total_number_of_empty_containers / (star.number_of_working_surfaces * 2))
    # 如果是第一个核电站，则不需要计算公海换装时间
    if star.nuclear_power_plant_list[0].id == star.target_nuclear_power_plant.id:
        time_of_transport_empty_containers_from_cargo_ship_to_the_dock = 0
    # 由于码头到暂存地运输时间短，可忽略不计

    # 开始装乏燃料的日期
    date_of_starting_to_load_spent_fuel = star.start_date + timedelta(days=(shipping_time_from_star_point_to_target_nuclear_power_plant + time_of_transport_empty_containers_from_cargo_ship_to_the_dock))

    # 1、将这个日期之后每10天为一个单位（或者叫做一列），得到每个单位的开始日期和结束日期 2、遍历每个单位，遍历每个机组的每个时间窗，如果当前单位的开始日期或结束日期在这个时间窗内，或者时间窗在这个单位内，则为不可安装乏燃料的单位
    start_and_end_date_list_by_ten_days_as_a_column = []
    date_of_starting_to_load_spent_fuel_for_computed = copy.deepcopy(date_of_starting_to_load_spent_fuel)  # 深度拷贝，否则下面的计算会改变date_of_starting_to_load_spent_fuel的值
    # 因为是以10天为一个单位，如果是189天，则按照180天
    number_of_columns = star.upper_limit_days // 10
    for _ in range(number_of_columns):
        start_and_end_date = []
        start_and_end_date.append(date_of_starting_to_load_spent_fuel_for_computed)
        date_of_starting_to_load_spent_fuel_for_computed = date_of_starting_to_load_spent_fuel_for_computed + timedelta(days=9)
        start_and_end_date.append(date_of_starting_to_load_spent_fuel_for_computed)
        start_and_end_date_list_by_ten_days_as_a_column.append(start_and_end_date)
        date_of_starting_to_load_spent_fuel_for_computed = date_of_starting_to_load_spent_fuel_for_computed + timedelta(days=1)

    flag_Matrix = []  # 将目标核电站各机组的flag_list放到一个列表中，方便后面建模求解
    # 得到目标核电站各机组的flag_list
    for e_unit in star.unit_list_of_target_nuclear_power_plant:
        # 列表中元素为1表示当前单位可以安排空容器装料，为0表示不可以装料
        e_unit.flag_list = [1 for _ in range(number_of_columns)]
        for e_start_and_end_date_index, e_start_and_end_date in enumerate(start_and_end_date_list_by_ten_days_as_a_column):
            for e_non_loading_time_window in e_unit.non_loading_time_window_list:
                if e_non_loading_time_window.non_loading_start_time <= e_start_and_end_date[0] <= e_non_loading_time_window.non_loading_end_time:
                    e_unit.flag_list[e_start_and_end_date_index] = 0
                if e_non_loading_time_window.non_loading_start_time <= e_start_and_end_date[1] <= e_non_loading_time_window.non_loading_end_time:
                    e_unit.flag_list[e_start_and_end_date_index] = 0
                # 如果不可装料时间窗的天数小于10天，特殊处理（即时间窗在这个单位内，则为不可安装乏燃料的单位）
                if e_non_loading_time_window.non_loading_start_time >= e_start_and_end_date[0] and e_non_loading_time_window.non_loading_end_time <= e_start_and_end_date[1]:
                    e_unit.flag_list[e_start_and_end_date_index] = 0
        flag_Matrix.append(e_unit.flag_list)






    model = Model()  # 创建模型

    number_of_units = len(star.unit_list_of_target_nuclear_power_plant)
    number_of_variables = number_of_columns * number_of_units

    variable_name_list = [i for i in range(1, number_of_variables + 1)]
    variable_list = model.binary_var_list(variable_name_list, lb=0, name='X')  # 创建变量列表

    # 将variable_name_list中的元素变成行数为number_of_units、列数为number_of_columns的列表
    variable_Matrix = [[] for _ in range(number_of_units)]
    for i in range(number_of_units):
        for j in range(number_of_columns):
            variable_Matrix[i].append(variable_list[i * number_of_columns + j])

    # 需要使得计划的天数越少越好
    # 各机组可安排空容器数上限、安排的空容器的总数量、同一个列安排的空容器数量不超过作业面数量
    obj_function = 0
    for i in range(number_of_columns):
        for j in range(number_of_units):
            obj_function = obj_function + variable_Matrix[j][i] * flag_Matrix[j][i] * ((number_of_columns - i) * 10 + np.random.randint(1, 10))
    model.maximize(obj_function)

    for i in range(number_of_columns):
        working_surface_left_equation = 0
        for j in range(number_of_units):
            working_surface_left_equation = working_surface_left_equation + variable_Matrix[j][i] * flag_Matrix[j][i]
        model.add_constraint(working_surface_left_equation <= star.number_of_working_surfaces)


    upper_limit_of_empty_containers_of_each_unit = []
    for e_unit in star.unit_list_of_target_nuclear_power_plant:
        upper_limit_of_empty_containers_of_each_unit.append(e_unit.upper_limit_of_empty_containers)

    for i in range(number_of_units):
        empty_containers_num_left_equation = 0
        for j in range(number_of_columns):
            empty_containers_num_left_equation = empty_containers_num_left_equation + variable_Matrix[i][j] * flag_Matrix[i][j]
        model.add_constraint(empty_containers_num_left_equation <= upper_limit_of_empty_containers_of_each_unit[i])

    total_number_of_empty_containers_left_equation = 0
    for i in range(number_of_units):
        for j in range(number_of_columns):
            total_number_of_empty_containers_left_equation = total_number_of_empty_containers_left_equation + variable_Matrix[i][j] * flag_Matrix[i][j]
    model.add_constraint(total_number_of_empty_containers_left_equation == star.total_number_of_empty_containers)

    sol = model.solve()  # 求解模型
    result = []
    for rt in range(number_of_variables):
        result.append(int(sol.get_value(variable_list[rt])))

    result_Matrix = [[] for _ in range(number_of_units)]
    for i in range(number_of_units):
        for j in range(number_of_columns):
            result_Matrix[i].append(result[i * number_of_columns + j])
    # print(result_Matrix)






    rectangle_list = []
    for e_unit_index, e_unit in enumerate(star.unit_list_of_target_nuclear_power_plant):
        for e_non_loading_time_window in e_unit.non_loading_time_window_list:
            horizontal_axis = (e_non_loading_time_window.non_loading_start_time - star.start_date).days
            width = (e_non_loading_time_window.non_loading_end_time - e_non_loading_time_window.non_loading_start_time).days
            # x坐标、宽度、y坐标、颜色、机组名、开始日期、结束日期
            rectangle = Rectangle("", horizontal_axis, width, e_unit_index + 1, "red", e_non_loading_time_window.non_loading_start_time, e_non_loading_time_window.non_loading_end_time, e_unit.unit_name, "", "", "")
            rectangle_list.append(rectangle)

    for e_unit_index, e_unit in enumerate(star.unit_list_of_target_nuclear_power_plant):
        for el_index, el in enumerate(result_Matrix[e_unit_index]):
            if el == 1:
                starting_to_load_date_minus_start_date = (date_of_starting_to_load_spent_fuel - star.start_date).days
                horizontal_axis = starting_to_load_date_minus_start_date + el_index * 10
                color = "forestgreen"
                if el_index % 2 == 0:
                    color = "deepskyblue"
                rectangle = Rectangle("", horizontal_axis, 10, e_unit_index + 1, color, date_of_starting_to_load_spent_fuel + timedelta(days=10 * el_index), date_of_starting_to_load_spent_fuel + timedelta(days=10 * el_index + 9), e_unit.unit_name, "", "", "")
                rectangle_list.append(rectangle)

    single_transportation_plan_version = SingleTransportationPlanVersion("", "", target_nuclear_power_plant.nuclear_power_plant_name, star.start_date, star.end_date, star.upper_limit_days,"", "")

    # 将 Rectangle 对象列表转换为字典列表
    rectangle_list_dict = [rect.to_dict() for rect in rectangle_list]

    # 将 SingleTransportationPlanVersion 对象转换为字典
    single_transportation_plan_version_dict = single_transportation_plan_version.to_dict()

    dict_of_result = {
        "rectangle_list": rectangle_list_dict,
        "single_transportation_plan_version": single_transportation_plan_version_dict,
    }




    # # 甘特图
    # import matplotlib.pyplot as plt
    # from matplotlib.pyplot import MultipleLocator
    #
    # plt.rcParams['font.sans-serif'] = ['SimHei']
    # plt.rcParams['axes.unicode_minus'] = False
    #
    # plt.figure(figsize=(20, 8), dpi=200)
    # # plt.axvline(x=0, color='b', linestyle='-')
    # # plt.axvline(x=star.upper_limit_days - 1, color='b', linestyle='-')
    # # 设置x轴的范围
    # # plt.xlim(0, star.upper_limit_days - 1)
    #
    # for e_rectangle in rectangle_list:
    #     plt.barh(e_rectangle.vertical_axis, height=0.8, width=e_rectangle.width, left=e_rectangle.horizontal_axis,
    #              color=e_rectangle.color)
    #     if e_rectangle.color != "red":
    #         plt.annotate(e_rectangle.start_date, xy=(e_rectangle.horizontal_axis, e_rectangle.vertical_axis))
    #
    # dict_of_each_unit = []
    # for e_rectangle in rectangle_list:
    #     if {e_rectangle.unit_name: e_rectangle.vertical_axis} not in dict_of_each_unit:
    #         dict_of_each_unit.append({e_rectangle.unit_name: e_rectangle.vertical_axis})
    # # 根据值的从小到大排序
    # dict_of_each_unit.sort(key=lambda x: list(x.values())[0])
    # # 获取所有的键组成列表，列表中的元素为所有字典的键
    # name_of_each_unit = [list(d.keys())[0] for d in dict_of_each_unit]
    #
    #
    # date_for_labels = []
    # start_date_for_computed = copy.copy(single_transportation_plan_version.start_date_of_plan)
    # date_for_labels.append(start_date_for_computed)
    # for i in range(single_transportation_plan_version.upper_limit_days // 10):
    #     start_date_for_computed += timedelta(days=10)
    #     date_for_labels.append(start_date_for_computed)
    #
    # plt.title(single_transportation_plan_version.nuclear_power_plant_name + "核电站乏燃料运输计划")
    # plt.yticks(ticks=range(1, len(name_of_each_unit) + 1), labels=name_of_each_unit)
    # plt.xticks(ticks=range(0, single_transportation_plan_version.upper_limit_days + 10, 10), labels=date_for_labels)
    #
    # plt.show()


    # 将列表转换成字符串
    # print(json.dumps(dict_of_result))
    return dict_of_result
    # except Exception as e:
    #     print(e)
    #     return None


if __name__ == '__main__':
    single_transportation_plan_condition = {
        "startDate":"2025-02-23",
        "upperLimitDays":180,
        "totalNumberOfEmptyContainers":20,
        "numberOfWorkingSurfaces":4,
        "targetNuclearPowerPlantId":4,
        "unitListByNuclearPowerPlantId":[
            {"id":13,"unitName":"D1","unitDescribe":"","upperLimitOfEmptyContainers":8,"nuclearPowerPlantId":4,"createTime":1739187297000,"updateTime":1739187297000},
            {"id":14,"unitName":"D2","unitDescribe":"","upperLimitOfEmptyContainers":5,"nuclearPowerPlantId":4,"createTime":1739187300000,"updateTime":1739187300000},
            {"id":15,"unitName":"D3","unitDescribe":"","upperLimitOfEmptyContainers":5,"nuclearPowerPlantId":4,"createTime":1739187303000,"updateTime":1739187303000},
            {"id":16,"unitName":"D4","unitDescribe":"","upperLimitOfEmptyContainers":1,"nuclearPowerPlantId":4,"createTime":1739187305000,"updateTime":1739187305000},
            {"id":17,"unitName":"D5","unitDescribe":"","upperLimitOfEmptyContainers":5,"nuclearPowerPlantId":4,"createTime":1739187308000,"updateTime":1739187308000},
            {"id":18,"unitName":"D6","unitDescribe":"","upperLimitOfEmptyContainers":2,"nuclearPowerPlantId":4,"createTime":1739187310000,"updateTime":1739187310000}],
        "nuclearPowerPlantList":[
            {"id":1,"nuclearPowerPlantName":"HYH","nuclearPowerPlantDescribe":None,"createTime":1739182490000,"updateTime":1739182490000},
            {"id":2,"nuclearPowerPlantName":"CN","nuclearPowerPlantDescribe":None,"createTime":1739182499000,"updateTime":1739182499000},
            {"id":3,"nuclearPowerPlantName":"ND","nuclearPowerPlantDescribe":None,"createTime":1739182505000,"updateTime":1739182505000},
            {"id":4,"nuclearPowerPlantName":"DYW","nuclearPowerPlantDescribe":None,"createTime":1739182509000,"updateTime":1739182509000},
            {"id":5,"nuclearPowerPlantName":"TS","nuclearPowerPlantDescribe":None,"createTime":1739182512000,"updateTime":1739182512000},
            {"id":6,"nuclearPowerPlantName":"YJ","nuclearPowerPlantDescribe":None,"createTime":1739182515000,"updateTime":1739182515000},
            {"id":7,"nuclearPowerPlantName":"FCG","nuclearPowerPlantDescribe":None,"createTime":1739182519000,"updateTime":1739182519000}]}

    dict_of_result = single_transportation_plan(single_transportation_plan_condition)
    print(dict_of_result)